package nl.Viewer;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.border.Border;

import nl.Viewer.Dialogs.LoginDialog;
import nl.Viewer.Dialogs.DatasetSelection.ModalitySelectionPanel;
import nl.Viewer.SerializedObjects.Dataset;
import nl.Viewer.SerializedObjects.LookmarkInfo;
import nl.Viewer.Sessions.SessionManager;
import nl.Viewer.Sessions.Communication.ConnectionStateListener;
import nl.Viewer.ViewManager.Controllers.GenericController;
import nl.Viewer.ViewManager.Objects.ControllerObject;
import nl.Viewer.ViewManager.Objects.ControllerPair;

/**
 * The GUI class manages all top-level interface elements (like the menubar, toolbar, statusbar and left/right panels)
 * 
 * @author Jeroen Verschuur
 * 
 */

public class GUI extends JPanel implements Runnable, ConnectionStateListener, ActionListener {
   private static final long       serialVersionUID         = -1;
   private Component               orientationTabComponent  = null;
   private Component               parameterTabComponent    = null;
   private JToolBar                toolBar                  = new JToolBar();
   private StatusBar               statusBar;
   private JSplitPane              leftRightSplitPane;
   private final static int        leftRightDividerLocation = 300;

   private Dataset                 selectedDataset          = null;

   public JPanel                   datasetSelectionPanel;
   private JTabbedPane             tabPane                  = new JTabbedPane( JTabbedPane.BOTTOM );
   /**
    * statusbar properties:
    */
   public String                   message                  = null;

   // -1 = indeterminate, 0-100 is some progress, < -1 is hidden
   public int                      progress                 = -2;
   public String                   server                   = null;

   public int                      openSessions             = 0;
   public int                      remainingJobs            = 0;

   /**
    * Menu bar properties
    * 
    */
   private JMenuItem               connectMenuItem          = null;
   private JMenuItem               disconnectMenuItem       = null;
   private JMenuItem               exitMenuItem             = null;
   private JMenu                   synchronizationMenu      = null;

   private JMenu                   actionMenu               = null;
   private final static String     CREATE_LOOKMARK          = "Create lookmark";
   private JMenuItem               createLookmarkItem       = null;

   private JMenu                   lookmarkMenu             = null;
   private ArrayList<LookmarkInfo> lookmarksAvailable       = null;

   private final JMenuItem         noOptions;

   private Object                  updateLockObject         = new Object();
   private volatile boolean        isRunning                = true;

   public GUI() {
      super( new BorderLayout() );

      createToolbar();
      createStatusbar();
      createLeftAndRightPanel();
      new Thread( this ).start();

      noOptions = new JMenuItem( "<no options>" );
      noOptions.setEnabled( false );
   }

   public void shutdown() {
      isRunning = false;
   }

   public void run() {
      statusBar.updateInfo();
      while( isRunning ) {
         synchronized( updateLockObject ) {
            try {
               updateLockObject.wait();
            }
            catch( InterruptedException ie ) {
               // ignore
            }
            statusBar.updateInfo();
         }
      }
   }

   public void doLogin() {
      // start with the login dialog
      LoginDialog loginDialog = new LoginDialog( Application.frame, this, Application.sessionManager );
      loginDialog.setVisible( true );

      if( loginDialog.isCanceled() == false && loginDialog.getSelectedDataset() != null ) {
         Dataset selectedDataset = loginDialog.getSelectedDataset();
         try {
            initializeInterface( selectedDataset );
         }
         catch( Exception e ) {
            Application.showErrorMessage( "Initialization error",
                  "Could not initialize the interface because of a communication error: " + e );
         }
      }
   }

   public void clearInterface() {
      // remove contents from the datasetSelectionPanel
      datasetSelectionPanel.removeAll();
      datasetSelectionPanel.invalidate();

      // remove all views
      clearRightPanel();

      // reset the vertical splitter
      leftRightSplitPane.setDividerLocation( 0 );
      leftRightSplitPane.setLastDividerLocation( leftRightDividerLocation );

      // set the window title
      Application.frame.setTitle( Application.applicationName );

      selectedDataset = null;
      lookmarksAvailable = null;
      setLookmarkMenu();
   }

   public synchronized void updateLookmarks() {
      try {
         this.lookmarksAvailable = Application.sessionManager.getLookmarks( selectedDataset );
         setLookmarkMenu();
      }
      catch( Exception e ) {
         System.out.println( "Lookmarks could not be fetched from the server: " + e );
      }
   }

   public void initializeInterface( Dataset dataset ) {
      clearInterface();

      this.selectedDataset = dataset;
      // create new contents
      datasetSelectionPanel.add( new ModalitySelectionPanel( dataset ), BorderLayout.CENTER );

      datasetSelectionPanel.invalidate();

      leftRightSplitPane.setDividerLocation( leftRightDividerLocation );

      // set the window title
      Application.frame.setTitle( Application.applicationName + " - " + dataset.name );

      updateLookmarks();
   }

   public JMenuBar createMenuBar() {
      JMenuBar menuBar = new JMenuBar();
      JMenu menu;

      menu = new JMenu( "File" );
      menu.setMnemonic( KeyEvent.VK_F );

      connectMenuItem = new JMenuItem( "Connect", KeyEvent.VK_C );
      connectMenuItem.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_C, ActionEvent.ALT_MASK ) );
      connectMenuItem.addActionListener( this );
      menu.add( connectMenuItem );

      disconnectMenuItem = new JMenuItem( "Disconnect", KeyEvent.VK_D );
      disconnectMenuItem.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_D, ActionEvent.ALT_MASK ) );
      disconnectMenuItem.setEnabled( false );
      disconnectMenuItem.addActionListener( this );
      menu.add( disconnectMenuItem );

      menu.addSeparator();

      exitMenuItem = new JMenuItem( "Exit", KeyEvent.VK_X );
      exitMenuItem.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_X, ActionEvent.ALT_MASK ) );
      exitMenuItem.addActionListener( this );
      menu.add( exitMenuItem );
      menuBar.add( menu );

      actionMenu = new JMenu( "Actions" );
      actionMenu.setMnemonic( KeyEvent.VK_A );

      createLookmarkItem = new JMenuItem( CREATE_LOOKMARK );
      createLookmarkItem.setEnabled( false );
      createLookmarkItem.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_D, ActionEvent.CTRL_MASK ) );
      actionMenu.add( createLookmarkItem );

      actionMenu.addSeparator();

      synchronizationMenu = new JMenu( "Synchronization" );
      synchronizationMenu.setMnemonic( KeyEvent.VK_S );
      synchronizationMenu.setEnabled( false );

      actionMenu.add( synchronizationMenu );

      menuBar.add( actionMenu );

      lookmarkMenu = new JMenu( "Lookmarks" );
      lookmarkMenu.setMnemonic( KeyEvent.VK_L );

      menuBar.add( lookmarkMenu );

      return menuBar;

   }

   private void createToolbar() {
      add( toolBar, BorderLayout.PAGE_START );
   }

   private void createStatusbar() {
      statusBar = new StatusBar( this );
      add( statusBar, BorderLayout.PAGE_END );
   }

   private void createLeftAndRightPanel() {
      leftRightSplitPane = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT, createLeftPanel(), createRightPanel() );
      leftRightSplitPane.setLastDividerLocation( leftRightDividerLocation );
      leftRightSplitPane.setOneTouchExpandable( true );
      leftRightSplitPane.setDividerSize( 10 );
      leftRightSplitPane.setDividerLocation( 0 );
      add( leftRightSplitPane, BorderLayout.CENTER );
   }

   public void setOrientationTab( JPanel orientationPanel ) {
      /**
       * Set (when already existing) or create (when not existing) an orientation tab on the tabPane. Remove the tab
       * when the orientation parameter is specified as null.
       */
      if( orientationPanel != null ) {
         if( orientationTabComponent != null ) {
            if( orientationTabComponent != orientationPanel ) { // if we call setOrientationAnd...() twice
               int index = tabPane.indexOfComponent( orientationTabComponent );
               tabPane.setTabComponentAt( index, orientationPanel );
            }
         } else {
            tabPane.addTab( "Orientation", null, orientationPanel, "Orientation Panel" );
         }
      } else {
         if( orientationTabComponent != null ) {
            tabPane.remove( orientationTabComponent );
         }
      }
      orientationTabComponent = orientationPanel;
   }

   public void setParameterTab( JPanel parameterPanel ) {
      /**
       * Do the same for the parameter panel as we did with the orientation panel above
       */
      if( parameterPanel != null ) {
         if( parameterTabComponent != null ) {
            if( parameterTabComponent != parameterPanel ) { // in case we call this method twice
               // overwrite the tab
               int index = tabPane.indexOfComponent( parameterTabComponent );
               tabPane.setTabComponentAt( index, parameterPanel );
            }
         } else {
            tabPane.addTab( "Parameters", null, parameterPanel, "Parameter Panel" );
         }
      } else {
         if( parameterTabComponent != null ) {
            tabPane.remove( parameterTabComponent );
         }
      }
      parameterTabComponent = parameterPanel;

      // force repainting the component
      tabPane.validate();
      tabPane.repaint();
   }

   public void setSynchronizationMenu( GenericController activeController,
         ArrayList<ControllerPair> synchronizableControllers ) {
      synchronizationMenu.removeAll();
      if( synchronizableControllers == null ) {
         // remove all menu items and disable the menu
         // synchronizationMenu.add( noOptions );

         synchronizationMenu.setEnabled( false );
      } else {
         // insert all pairs of synchronizable controllers into the menu
         for( int i = 0; i < synchronizableControllers.size(); i++ ) {
            ControllerPair cp = synchronizableControllers.get( i );

            // check which of the two objects in the pair is NOT the active controller (as we want to display the other
            // controller in the menu)
            ControllerObject activeObject = cp.getObject1();
            ControllerObject targetObject = cp.getObject2();
            if( activeObject.getController() != activeController ) {
               targetObject = activeObject;
               activeObject = cp.getObject2();
            }

            // create a new menu item for the item we can synchronize with
            JMenuItem item = new JCheckBoxMenuItem(); // set the item name in the action below

            // we need finals for the anonymous abstractAction class below,
            // have the viewManager check if the pair still exists in runtime
            final GenericController source = activeController;
            final GenericController target = targetObject.getController();

            // check the checkbox if they are already synchronized
            item.setSelected( activeObject.isOtherControllerSynchronized() );

            item.setAction( new AbstractAction(
                  targetObject.getController().modality.getName() + " - " + targetObject.getController().visualization.getName() ) {
               private static final long serialVersionUID = 541525280036157624L;

               public void actionPerformed( ActionEvent e ) {
                  // have the viewManager toggle the synchronization status for this pair
                  if( !Application.viewManager.toggleSynchronization( source, target ) ) {
                     // notify the user
                     Application.showErrorMessage( "Synchronization error",
                           "Two views cannot both be synchronized to each other, disable the reverse synchronization first!" );

                     ( (JCheckBoxMenuItem)e.getSource() ).setSelected( !( (JCheckBoxMenuItem)e.getSource() ).isSelected() );
                  }
               }
            } );
            synchronizationMenu.add( item );
         }

         if( synchronizableControllers.size() == 0 ) {
            synchronizationMenu.add( noOptions );
         }
         synchronizationMenu.setEnabled( true );
      }
      synchronizationMenu.invalidate();
   }

   public void setActionMenu( GenericController activeController ) {
      actionMenu.setEnabled( true );
      // update the actions

      createLookmarkItem.removeActionListener( createLookmarkItem.getAction() );
      if( activeController == null ) {
         createLookmarkItem.setAction( new AbstractAction( CREATE_LOOKMARK ) {
            private static final long serialVersionUID = -8411985629598866774L;

            public void actionPerformed( ActionEvent e ) {
            }
         } );
         createLookmarkItem.setEnabled( false );
      } else {
         final GenericController finalActiveController = activeController;

         createLookmarkItem.setAction( new AbstractAction( CREATE_LOOKMARK ) {
            private static final long serialVersionUID = -1552963144355358023L;

            public void actionPerformed( ActionEvent e ) {
               new Thread() {
                  public void run() {
                     // ask the user the description
                     String response = JOptionPane.showInputDialog( null, null, "Enter lookmark description",
                           JOptionPane.QUESTION_MESSAGE );

                     if( response != null ) {
                        finalActiveController.createLookmark( true, response );
                        updateLookmarks();
                     }
                  }
               }.start();
            }
         } );
      }
   }

   public void setLookmarkMenu() {
      boolean hasLookmarks = false;
      lookmarkMenu.setEnabled( true );

      // add the lookmark items
      lookmarkMenu.removeAll();

      // add all available lookmarks
      if( lookmarksAvailable != null ) {
         for( LookmarkInfo li : lookmarksAvailable ) {

            final int lookmarkId = li.lookmarkId;
            final int visualizationId = li.visualizationId;
            final int modalityId = li.modalityId;

            JMenu lmm = new JMenu( li.description );

            JMenuItem openItem = new JMenuItem();
            openItem.setAction( new AbstractAction( "Open" ) {
               private static final long serialVersionUID = 4225302518864308837L;

               public void actionPerformed( ActionEvent e ) {
                  new Thread() {
                     public void run() {
                        Application.viewManager.openLookmark( lookmarkId, visualizationId, modalityId );
                     }
                  }.start();
               }
            } );

            lmm.add( openItem );

            if( li.deleteAllowed )
               lmm.add( new JMenuItem( "Delete" ) );

            lookmarkMenu.add( lmm );
            hasLookmarks = true;
         }
      }
      
      if( !hasLookmarks )
         lookmarkMenu.setEnabled( false );
   }

   private JComponent createLeftPanel() {
      JPanel emptyPanel = new JPanel( new GridBagLayout() );
      emptyPanel.add( new JLabel( "No dataset selected" ) );

      datasetSelectionPanel = new JPanel( new BorderLayout() );
      datasetSelectionPanel.add( emptyPanel );

      JSplitPane splitPane = new JSplitPane( JSplitPane.VERTICAL_SPLIT, tabPane, datasetSelectionPanel );
      splitPane.setDividerLocation( 250 );

      return splitPane;
   }

   private JComponent createRightPanel() {
      return Application.viewManager.getViewDesktop( this );
   }

   public void clearRightPanel() {
      Application.viewManager.clearViewPanel();
   }

   public void updateStatusBar() {
      statusBar.updateInfo();
   }

   public JSplitPane getLeftRightSplitPane() {
      return leftRightSplitPane;
   }

   public void updateGUI() {
      synchronized( updateLockObject ) {
         updateLockObject.notify();
      }
   }

   /********************************************************************************************************************
    * 
    * statusbar getters/setters
    * 
    */

   public String getMessage() {
      return message;
   }

   public void setMessage( String message ) {
      if( !message.startsWith( " " ) ) {
         this.message = " " + message;
      } else {
         this.message = message;
      }
      updateGUI();
   }

   public void resetMessage() {
      this.message = null;
      this.progress = -2;
      updateGUI();
   }

   public int getProgress() {
      return progress;
   }

   public void setProgress( int progress ) {
      this.progress = progress;
      updateGUI();
   }

   public String getServer() {
      return server;
   }

   private void setServer( String server ) {
      this.server = server;
      updateGUI();
   }

   public int getOpenSessions() {
      return Application.sessionManager.getSessionCount();
   }

   public int getRemainingJobs() {
      return remainingJobs;
   }

   public void serverConnected( SessionManager sessionManager, String serverAddress ) {
      setServer( serverAddress );
      connectMenuItem.setEnabled( false );
      disconnectMenuItem.setEnabled( true );
   }

   public void serverDisconnected( SessionManager sessionManager ) {
      setServer( null );
      connectMenuItem.setEnabled( true );
      disconnectMenuItem.setEnabled( false );

   }

   public void actionPerformed( ActionEvent e ) {
      if( e.getSource() == connectMenuItem ) {
         // user wants to connect to server, show the login dialog
         doLogin();
      } else if( e.getSource() == disconnectMenuItem ) {
         // user wants to disconnect
         // close the sessionManager, and all views/sessions, etc
         Application.sessionManager.shutdown();
         clearInterface();
      } else if( e.getSource() == exitMenuItem ) {
         // shutdown entire application
         Application.shutdown();
      } else {
         System.out.println( "Action command= " + e.getActionCommand() );
      }
   }

   public Dataset getSelectedDataset() {
      return selectedDataset;
   }
}

class StatusBar extends JPanel {
   private static final long serialVersionUID = -1;
   JLabel                    messageLabel;
   JProgressBar              progressBar;
   JLabel                    serverLabel;
   JLabel                    openSessions;
   JLabel                    remainingJobs;
   GUI                       gui;
   JPanel                    message;
   JPanel                    parameters;
   Border                    splitBorder      = BorderFactory.createCompoundBorder( BorderFactory.createMatteBorder( 0,
                                                    1, 0, 0, Color.GRAY ), BorderFactory.createMatteBorder( 0, 1, 0, 0,
                                                    Color.WHITE ) );

   public JPanel createFiller() {
      JPanel filler = new JPanel();
      filler.setPreferredSize( new Dimension( 5, 20 ) );
      filler.setBorder( splitBorder );
      return filler;
   }

   public StatusBar( GUI gui ) {
      super( new BorderLayout() );

      this.gui = gui;
      setPreferredSize( new Dimension( 100, 22 ) );

      message = new JPanel( new BorderLayout() );
      // message.setPreferredSize(new Dimension(400, 20));
      // message.setBorder(new LineBorder(Color.RED));

      messageLabel = new JLabel( " Ready" );
      message.add( messageLabel, BorderLayout.LINE_START );

      JPanel progressPanel = new JPanel( new FlowLayout( FlowLayout.RIGHT, 5, 2 ) );
      progressBar = new JProgressBar();
      // progressPanel.setPreferredSize(new Dimension(150, 20));

      progressPanel.add( progressBar );

      parameters = new JPanel();
      parameters.setLayout( new BoxLayout( parameters, BoxLayout.LINE_AXIS )/*
                                                                               * new FlowLayout(FlowLayout.RIGHT, 5, 0)
                                                                               */);
      // parameters.setBorder(new LineBorder(Color.RED));
      parameters.add( createFiller() );

      serverLabel = new JLabel( "Not connected" );
      serverLabel.setPreferredSize( new Dimension( 250, 22 ) );

      parameters.add( serverLabel );
      parameters.add( createFiller() );

      openSessions = new JLabel( "0" );
      openSessions.setPreferredSize( new Dimension( 20, 22 ) );
      parameters.add( openSessions );
      parameters.add( createFiller() );

      remainingJobs = new JLabel( "0" );
      remainingJobs.setPreferredSize( new Dimension( 20, 22 ) );
      parameters.add( remainingJobs );

      this.add( message, BorderLayout.LINE_START );
      this.add( progressPanel, BorderLayout.CENTER );
      this.add( parameters, BorderLayout.LINE_END );
   }

   public void updateInfo() {
      String message = gui.getMessage();
      if( message == null )
         message = " Ready";
      messageLabel.setText( message );

      if( progressBar.isVisible() && gui.getProgress() < -1 ) {
         // hide the progress bar
         progressBar.setVisible( false );
      }
      if( !progressBar.isVisible() && gui.getProgress() >= -1 ) {
         progressBar.setVisible( true );
      }

      if( gui.getProgress() == -1 ) {
         progressBar.setIndeterminate( true );
      } else {
         progressBar.setIndeterminate( false );
         progressBar.setValue( gui.getProgress() );
      }

      if( gui.getServer() == null ) {
         serverLabel.setText( "<Not Connected>" );
      } else {
         serverLabel.setText( "Connected to: " + gui.getServer() );
      }

      openSessions.setText( "" + gui.getOpenSessions() );

      remainingJobs.setText( "" + gui.getRemainingJobs() );

      validate();
   }

}
